// SPDX-License-Identifier: MIT
/*
Copyright 2011 by Matthieu Boutier and Juliusz Chroboczek
*/

/* FRR's includes */
#include <zebra.h>
#include "command.h"
#include "zclient.h"
#include "stream.h"

/* babel's includes*/
#include "babel_zebra.h"
#include "babel_interface.h"
#include "xroute.h"
#include "util.h"

void babelz_zebra_init(void);


/* we must use a pointer because of zclient.c's functions (new, free). */
struct zclient *babel_zclient;

/* Debug types */
static const struct {
	int type;
	int str_min_len;
	const char *str;
} debug_type[] = { { BABEL_DEBUG_COMMON, 1, "common" }, { BABEL_DEBUG_KERNEL, 1, "kernel" },
		   { BABEL_DEBUG_FILTER, 1, "filter" }, { BABEL_DEBUG_TIMEOUT, 1, "timeout" },
		   { BABEL_DEBUG_IF, 1, "interface" },	{ BABEL_DEBUG_ROUTE, 1, "route" },
		   { BABEL_DEBUG_ALL, 1, "all" },	{ 0, 0, NULL } };

/* Zebra route add and delete treatment. */
static int babel_zebra_read_route(ZAPI_CALLBACK_ARGS)
{
	struct zapi_route api;

	if (zapi_route_decode(zclient->ibuf, &api) < 0)
		return -1;

	/* we completely ignore srcdest routes for now. */
	if (CHECK_FLAG(api.message, ZAPI_MESSAGE_SRCPFX))
		return 0;

	if (cmd == ZEBRA_REDISTRIBUTE_ROUTE_ADD) {
		babel_route_add(&api);
	} else {
		babel_route_delete(&api);
	}

	return 0;
}

/* [Babel Command] */
DEFUN (babel_redistribute_type,
       babel_redistribute_type_cmd,
       "[no] redistribute <ipv4 " FRR_IP_REDIST_STR_BABELD "|ipv6 " FRR_IP6_REDIST_STR_BABELD ">",
       NO_STR
       "Redistribute\n"
       "Redistribute IPv4 routes\n"
       FRR_IP_REDIST_HELP_STR_BABELD
       "Redistribute IPv6 routes\n"
       FRR_IP6_REDIST_HELP_STR_BABELD)
{
	int negate = 0;
	int family;
	int afi;
	int type;
	int idx = 0;

	if (argv_find(argv, argc, "no", &idx))
		negate = 1;
	argv_find(argv, argc, "redistribute", &idx);
	family = str2family(argv[idx + 1]->text);
	if (family < 0)
		return CMD_WARNING_CONFIG_FAILED;

	afi = family2afi(family);
	if (!afi)
		return CMD_WARNING_CONFIG_FAILED;

	type = proto_redistnum(afi, argv[idx + 2]->text);
	if (type < 0) {
		vty_out(vty, "Invalid type %s\n", argv[idx + 2]->arg);
		return CMD_WARNING_CONFIG_FAILED;
	}

	if (!negate)
		zclient_redistribute(ZEBRA_REDISTRIBUTE_ADD, babel_zclient, afi, type, 0,
				     VRF_DEFAULT);
	else {
		zclient_redistribute(ZEBRA_REDISTRIBUTE_DELETE, babel_zclient, afi, type, 0,
				     VRF_DEFAULT);
		/* perhaps should we remove xroutes having the same type... */
	}
	return CMD_SUCCESS;
}

#ifndef NO_DEBUG
/* [Babel Command] */
DEFUN (debug_babel,
       debug_babel_cmd,
       "debug babel <common|kernel|filter|timeout|interface|route|all>",
       "Enable debug messages for specific or all part.\n"
       "Babel information\n"
       "Common messages (default)\n"
       "Kernel messages\n"
       "Filter messages\n"
       "Timeout messages\n"
       "Interface messages\n"
       "Route messages\n"
       "All messages\n")
{
	int i;

	for (i = 0; debug_type[i].str != NULL; i++) {
		if (strncmp(debug_type[i].str, argv[2]->arg, debug_type[i].str_min_len) == 0) {
			SET_FLAG(debug, debug_type[i].type);
			return CMD_SUCCESS;
		}
	}

	vty_out(vty, "Invalid type %s\n", argv[2]->arg);

	return CMD_WARNING_CONFIG_FAILED;
}

/* [Babel Command] */
DEFUN (no_debug_babel,
       no_debug_babel_cmd,
       "no debug babel <common|kernel|filter|timeout|interface|route|all>",
       NO_STR
       "Disable debug messages for specific or all part.\n"
       "Babel information\n"
       "Common messages (default)\n"
       "Kernel messages\n"
       "Filter messages\n"
       "Timeout messages\n"
       "Interface messages\n"
       "Route messages\n"
       "All messages\n")
{
	int i;

	for (i = 0; debug_type[i].str; i++) {
		if (strncmp(debug_type[i].str, argv[3]->arg, debug_type[i].str_min_len) == 0) {
			UNSET_FLAG(debug, debug_type[i].type);
			return CMD_SUCCESS;
		}
	}

	vty_out(vty, "Invalid type %s\n", argv[3]->arg);

	return CMD_WARNING_CONFIG_FAILED;
}
#endif /* NO_DEBUG */

/* Output "debug" statement lines, if necessary. */
int debug_babel_config_write(struct vty *vty)
{
#ifdef NO_DEBUG
	return 0;
#else
	int i, lines = 0;

	if (debug == BABEL_DEBUG_ALL) {
		vty_out(vty, "debug babel all\n");
		lines++;
	} else {
		for (i = 0; debug_type[i].str != NULL; i++) {
			if (debug_type[i].type != BABEL_DEBUG_ALL &&
			    CHECK_FLAG(debug, debug_type[i].type)) {
				vty_out(vty, "debug babel %s\n", debug_type[i].str);
				lines++;
			}
		}
	}

	if (lines) {
		vty_out(vty, "!\n");
		lines++;
	}
	return lines;
#endif /* NO_DEBUG */
}

DEFUN_NOSH (show_debugging_babel,
	    show_debugging_babel_cmd,
	    "show debugging [babel]",
	    SHOW_STR
	    DEBUG_STR
	    "Babel")
{
	vty_out(vty, "BABEL debugging status\n");

	debug_babel_config_write(vty);

	cmd_show_lib_debugs(vty);

	return CMD_SUCCESS;
}

static void babel_zebra_connected(struct zclient *zclient)
{
	zclient_send_reg_requests(zclient, VRF_DEFAULT);
}

static zclient_handler *const babel_handlers[] = {
	[ZEBRA_INTERFACE_ADDRESS_ADD] = babel_interface_address_add,
	[ZEBRA_INTERFACE_ADDRESS_DELETE] = babel_interface_address_delete,
	[ZEBRA_REDISTRIBUTE_ROUTE_ADD] = babel_zebra_read_route,
	[ZEBRA_REDISTRIBUTE_ROUTE_DEL] = babel_zebra_read_route,
};

void babelz_zebra_init(void)
{
	babel_zclient = zclient_new(master, &zclient_options_default, babel_handlers,
				    array_size(babel_handlers));
	zclient_init(babel_zclient, ZEBRA_ROUTE_BABEL, 0, &babeld_privs);

	babel_zclient->zebra_connected = babel_zebra_connected;

	install_element(BABEL_NODE, &babel_redistribute_type_cmd);
	install_element(ENABLE_NODE, &debug_babel_cmd);
	install_element(ENABLE_NODE, &no_debug_babel_cmd);
	install_element(CONFIG_NODE, &debug_babel_cmd);
	install_element(CONFIG_NODE, &no_debug_babel_cmd);

	install_element(ENABLE_NODE, &show_debugging_babel_cmd);
}

void babel_zebra_close_connexion(void)
{
	zclient_stop(babel_zclient);
	zclient_free(babel_zclient);
}
